package com.ds.infrastructure.hyperspace.container.endpoint.entry;

import com.ds.infrastructure.hyperspace.common.EndpointProtocolEnum;
import com.ds.infrastructure.hyperspace.common.config.EntryNodeConfig;
import com.ds.infrastructure.hyperspace.common.config.EntryProtocolConfig;
import com.ds.infrastructure.hyperspace.common.config.TlsConfig;
import com.ds.infrastructure.hyperspace.container.flow.context.HyperspaceDuplexFlowContext;
import com.ds.infrastructure.hyperspace.container.internal.NettyContainer;
import com.ds.infrastructure.hyperspace.container.internal.http2.HyperspaceHttp2Codec;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.ssl.ApplicationProtocolNames;
import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler;
import io.netty.handler.ssl.SslContext;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import static com.ds.infrastructure.hyperspace.common.EndpointProtocolEnum.*;
import static com.ds.infrastructure.hyperspace.container.endpoint.NettyHandlerName.*;

/**
 * @author  Weishaoying
 */
@Slf4j
public class NettyEntryEndpointChannelInitializer extends ChannelInitializer<Channel> {

    private final NettyContainer nettyContainer;

    @Getter
    private final EntryNodeConfig entryNodeConfig;

    private final EntryProtocolConfig protocolConfig;

    private SslContext sslContext;

    public NettyEntryEndpointChannelInitializer(NettyContainer nettyContainer,
            EntryNodeConfig entryNodeConfig) {
        this.nettyContainer = nettyContainer;
        this.entryNodeConfig = entryNodeConfig;
        this.protocolConfig = entryNodeConfig.getProtocolConfig();
        if (entryNodeConfig.getProtocol().useTls()) {
            TlsConfig tlsConfig = entryNodeConfig.getTlsConfig();
            if (tlsConfig != null) {
                this.sslContext = nettyContainer.initSslServerContext(tlsConfig);
            } else {
                this.sslContext = nettyContainer.getSslServerContext();
            }
        }

    }

    @Override
    protected void initChannel(Channel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        if (sslContext != null) {
            pipeline.addLast(sslContext.newHandler(ch.alloc()));
            pipeline.addLast(new SslNegotiationHandler());
        } else {
            if (entryNodeConfig.getProtocol().getIndex() >= HTTP2.getIndex()) {
                configureHttp2Pipeline(pipeline);
            } else {
                configureHttp1Pipeline(pipeline);
            }
        }
    }

    public void switchProtocolFromHttp(ChannelPipeline pipeline,
            HyperspaceDuplexFlowContext duplexFlowContext,
            EndpointProtocolEnum to) {
        if (to.getIndex() <= WEBSOCKETS.getIndex()) {
            pipeline.remove(ENTRY_HTTP1_CODEC_HANDLER_NAME);
            if (pipeline.get(COMMON_HTTP1_AGGREGATOR_HANDLER_NAME) != null) {
                pipeline.remove(COMMON_HTTP1_AGGREGATOR_HANDLER_NAME);
            }
            duplexFlowContext.setEndpointProtocol(to);
            return;
        }
        if (to.getIndex() >= HTTP2.getIndex()) {
            pipeline.remove(ENTRY_HTTP1_CODEC_HANDLER_NAME);
            if (pipeline.get(COMMON_HTTP1_AGGREGATOR_HANDLER_NAME) != null) {
                pipeline.remove(COMMON_HTTP1_AGGREGATOR_HANDLER_NAME);
            }
            if (pipeline.get(ENTRY_HANDLER_ADAPTER_NAME) != null) {
                pipeline.remove(ENTRY_HANDLER_ADAPTER_NAME);
            }
            configureHttp2Pipeline(pipeline);
            AbstractEntryProtocolAdapter entryProtocolAdapter =
                    (AbstractEntryProtocolAdapter)pipeline.get(ENTRY_HANDLER_ADAPTER_NAME);
            entryProtocolAdapter.resetChannelActiveState(
                    pipeline.context(entryProtocolAdapter), duplexFlowContext);
            duplexFlowContext.setEndpointProtocol(to);
        }
    }

    private void configureHttp1Pipeline(ChannelPipeline pipeline) {
        pipeline.addLast(ENTRY_HTTP1_CODEC_HANDLER_NAME, new HttpServerCodec(
                protocolConfig.getMaxInitialLineLength(), protocolConfig.getMaxHeaderSize(),
                protocolConfig.getMaxChunkSize(),
                false, protocolConfig.getInitialBufferSize()));
        if (protocolConfig.isEnableBodyAggregator()) {
            pipeline.addLast(COMMON_HTTP1_AGGREGATOR_HANDLER_NAME,
                    new HttpObjectAggregator(protocolConfig.getMaxBodySize()));
        }
        pipeline.addLast(ENTRY_HANDLER_ADAPTER_NAME,
                new Http1EntryProtocolAdapter(nettyContainer, this));
    }

    private void configureHttp2Pipeline(ChannelPipeline pipeline) {
        HyperspaceHttp2Codec hyperspaceHttp2Codec = new HyperspaceHttp2Codec(true, protocolConfig);
        pipeline.addLast(ENTRY_HTTP2_CODEC_HANDLER_ADAPTER_NAME, hyperspaceHttp2Codec);
        pipeline.addLast(ENTRY_HANDLER_ADAPTER_NAME,
                new Http2EntryProtocolAdapter(nettyContainer, entryNodeConfig, hyperspaceHttp2Codec));
    }

    private class SslNegotiationHandler extends ApplicationProtocolNegotiationHandler {

        protected SslNegotiationHandler() {
            super(ApplicationProtocolNames.HTTP_1_1);
        }

        @Override
        protected void configurePipeline(ChannelHandlerContext ctx, String protocol) throws Exception {
            ChannelPipeline pipeline = ctx.pipeline();
            if (ApplicationProtocolNames.HTTP_2.equals(protocol)) {
                configureHttp2Pipeline(pipeline);
                return;
            }

            if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) {
                configureHttp1Pipeline(pipeline);
                return;
            }
            log.warn("Unsupport protocol [{}]", protocol);
            ctx.flush().close();
        }
    }

}

